#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include <mutex>
#include <unistd.h>
namespace BlockQueueModule
{
    static const int gac = 10;
    template <typename T>
    class BlockQueue
    {
    private:
        bool IsFull() { return _q.size() == _cap; }
        bool IsEmpty() { return _q.empty(); }

    public:
        BlockQueue(int cap = gac) : _cap(cap), _cwait_num(0), _pwait_num(0)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_productor_cond, nullptr);
            pthread_cond_init(&_consumer_cond, nullptr);
        }

        void Equeue(const T &in)
        {
            pthread_mutex_lock(&_mutex);
            while (IsFull())
            {
                _pwait_num++;
                printf("producer进入等待状态\n");
                pthread_cond_wait(&_productor_cond, &_mutex);
                _pwait_num--;
            }
            printf("producer进入执行状态\n");
            _q.push(in);
            if (_cwait_num)
            {
                pthread_cond_signal(&_consumer_cond);
            }
            pthread_mutex_unlock(&_mutex);
        }

        // 消费者
        void Pop(T *out)
        {
            pthread_mutex_lock(&_mutex);
            while (IsEmpty())
            {
                _cwait_num++;
                printf("consumer进入等待状态\n");
                pthread_cond_wait(&_consumer_cond, &_mutex);
                _cwait_num--;
            }
            printf("consumer进入执行任务状态\n");
            *out = _q.front();
            _q.pop();
            if (_pwait_num)
            {
                pthread_cond_signal(&_productor_cond);
            }
            pthread_mutex_unlock(&_mutex);
        }

        ~BlockQueue()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_productor_cond);
            pthread_cond_destroy(&_consumer_cond);
        }

    private:
        std::queue<T> _q;               // 保存数据的容器,临界资源
        int _cap;                       // bq的最大容量
        pthread_mutex_t _mutex;         // 互斥,lock和unlock函数就可以确定
        pthread_cond_t _productor_cond; // 生产者条件变量,用于决定生产者位于等待状态还是执行任务状态
        pthread_cond_t _consumer_cond;  // 消费者条件变量,用于决定消费者位于等待状态还是执行任务状态

        int _cwait_num;
        int _pwait_num;
    };
}

//============================================================================================
namespace BlockQueueModule
{
    static const int gac = 10;
    template <typename T>
    class BlockQueue
    {
    private:
        bool IsFull()
        {
            return _q.size() == _cap;
        }
        bool IsEmpty()
        {
            return _q.empty();
        }

    public:
        BlockQueue(int cap = gac) : _cap(cap), _cwait_num(0), _pwait_num(0)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_productor_cond, nullptr);
            pthread_cond_init(&_consumer_cond, nullptr);
        }

        // 生产者
        // 生产分为三大步骤,即生产,派发产物,通知消费者
        // 生产和派发产物由主线程发送信号决定,通知消费者由生产者发送信号给消费者
        void Equeue(const T &in)
        {
            pthread_mutex_lock(&_mutex);
            // 1.生产数据是有条件的,队列满就不可以放入
            if (IsFull())     // 如果满了 继续等待
            {                 // 2.等时 释放_mutex
                _pwait_num++; // 记录将要执行任务的由主线程生成的生产线程
                printf("生产者进入等待状态");
                pthread_cond_wait(&_productor_cond, &_mutex); // 等待被唤醒

                _pwait_num--; // 线程去执行任务,当前生产队列元素--
                              // 返回 线程被唤醒&&重新申请并持有锁
            } // 目前在临界区中(加锁解锁之间)等待是必然的,等待时一定持有锁
            // 4.队列不满 对阻塞队列生产
            printf("生产者进入执行状态");
            _q.push(in); // 将数据放入阻塞队列达到生产的效果

            // 上一个步骤生产完毕 现在肯定有数据
            if (_cwait_num)
            { // 消费者线程等待生产者线程唤醒去执行任务
                // 通知消费者获取资源
                pthread_cond_signal(&_consumer_cond);
            }
            pthread_mutex_unlock(&_mutex);
        }

        // 消费者
        void Pop(T *out)
        {
            pthread_mutex_lock(&_mutex);
            while (IsEmpty()) // 如果阻塞队列为空,则被pthread_wait_cond阻挡访问临界资源返回到等待队列
            // 使用while是为了防止伪唤醒
            {
                _cwait_num++; // 因为返回等待了,所以等待队列的成员线程++
                printf("消费者进入等待状态\n");
                pthread_cond_wait(&_consumer_cond, &_mutex); // 经过此代码 消费者线程进入等待状态
                printf("消费者进入执行任务状态\n");
                _cwait_num--; // 线程去执行任务,当前消费队列元素--
            } // 伪唤醒

            // 如果不为空,消费
            *out = _q.front();
            _q.pop();

            // 消费完成后等待阻塞队列一定有空间
            // 通知生产者补货
            if (_pwait_num)
            {
                // 通知生产者补充资源
                pthread_cond_signal(&_productor_cond);
            }

            pthread_mutex_unlock(&_mutex);
        }
        // 伪唤醒:即唤醒条件未达成目标线程依旧被唤醒了
        // 当使用pthread_cond_broadcast而不是...signal时,一次唤醒多个线程,但是由于对条件变量的判断是if,无法对线程进行一次判断,从而造成唤醒不达成
        // 所以为了防止伪唤醒,不使用if,而是while
        ~BlockQueue()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_productor_cond);
            pthread_cond_destroy(&_consumer_cond);
        }

    private:
        std::queue<T> _q;               // 保存数据的容器,临界资源
        int _cap;                       // bq的最大容量
        pthread_mutex_t _mutex;         // 互斥,lock和unlock函数就可以确定
        pthread_cond_t _productor_cond; // 生产者条件变量,用于决定生产者位于等待状态还是执行任务状态
        pthread_cond_t _consumer_cond;  // 消费者条件变量,用于决定消费者位于等待状态还是执行任务状态

        int _cwait_num;
        int _pwait_num;
    };
}
